﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using ElectronicCommerce.Framework.TcpHelper.Server;

namespace ElectronicCommerce.Framework.TcpHelper.Client
{
    /// <summary>
    /// Message Client IMessage
    /// </summary>
    public class MessageClient : IMessageClient
    {
        #region Fields

        private Socket _socket;

        private IMessageSerializer _serializer;

        private List<byte> messageBuffer = new List<byte>();

        private EndPoint _remoteEndPoint;

        public event EventHandler<AsyncCompletedEventArgs> ConnectionCompleted;

        public event EventHandler<AsyncCompletedEventArgs> SendCompleted;

        public event EventHandler<EventArgs> ConnectionLost;

        public event EventHandler<MessageRecievedEventArgs> MessageRecieved;

        #endregion

        #region Contructor

        private MessageClient() { }

        public MessageClient(string ip, int port, IMessageSerializer messageSerializer)
        {
            this._serializer = messageSerializer;
            this._remoteEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            this._socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        #endregion

        #region IMessageClient Members

        public bool IsConnected
        {
            get { return this._socket.Connected; }
        }

        public void ConnectAsync()
        {
            this.ConnectAsync(null);
        }

        public void ConnectAsync(object state)
        {
            if (!this.IsConnected)
            {
                var args = new SocketAsyncEventArgs { UserToken = state, RemoteEndPoint = _remoteEndPoint };
                args.Completed += SocketConnected;
                this._socket.ConnectAsync(args);
            }
        }

        public void Disconnect()
        {
            if (this._socket != null)
            {
                this._socket.Close();
            }
        }

        public void SendMessageAsync(MessageInfo message)
        {
            SendMessageAsync(message, null);
        }

        public void SendMessageAsync(MessageInfo message, object userState)
        {
            if ((this._socket == null) || !this.IsConnected)
            {
                throw new Exception();
            }

            var args = new SocketAsyncEventArgs();

            byte[] serializedMessage = this._serializer.Serialize(message);
            List<byte> messageByteList = serializedMessage.ToList();

            for (int i = 1023; i < serializedMessage.Length; i += 1023)
            {
                messageByteList.Insert(i, 0);
                i++;
            }

            messageByteList.Add(1);

            args.SetBuffer(messageByteList.ToArray(), 0, messageByteList.Count);
            args.Completed += this.SocketSendCompleted;
            args.UserToken = userState;

            this._socket.SendAsync(args);
        }

        #endregion

        #region Events

        private void OnMessageRecieved(MessageRecievedEventArgs e)
        {
            if (MessageRecieved != null)
            {
                MessageRecieved(this, e);
            }
        }

        private void OnConnectionLost()
        {
            if (ConnectionLost != null)
            {
                ConnectionLost(this, new EventArgs());
            }
        }

        private void OnConnected(AsyncCompletedEventArgs e)
        {
            if (ConnectionCompleted != null)
            {
                ConnectionCompleted(this, e);
            }
        }

        private void OnSocketConnected(object sender, SocketAsyncEventArgs e)
        {
            AsyncCompletedEventArgs asyncArgs;
            if (this.IsConnected)
            {
                var response = new byte[1024];
                e.SetBuffer(response, 0, response.Length);
                e.Completed -= this.OnSocketConnected;
                e.Completed += this.OnSocketReceive;

                this._socket.ReceiveAsync(e);
                asyncArgs = new AsyncCompletedEventArgs(null, false, e.UserToken);
                this.OnConnected(asyncArgs);
            }

            // TODO: Handle error while connection.
        }

        private void OnSocketReceive(object sender, SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred == 0)
            {
                this.OnConnectionLost();

                this._socket.Close();

                return;
            }

            this.messageBuffer.AddRange(e.Buffer.Take(e.BytesTransferred - 1));
            if (e.Buffer[e.BytesTransferred - 1] > 0)
            {
                this.OnMessageRecieved(
                    new MessageRecievedEventArgs(this._serializer.Deserialize(this.messageBuffer.ToArray())));
                this.messageBuffer.Clear();
            }

            this._socket.ReceiveAsync(e);
        }

        private void SocketSendCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (this.SendCompleted != null)
            {
                OnSendCompleted(new AsyncCompletedEventArgs(null, false, e.UserToken));
            }
        }

        private void OnSendCompleted(AsyncCompletedEventArgs e)
        {
            if (this.SendCompleted != null)
            {
                this.SendCompleted(this, e);
            }
        }

        private void SocketConnected(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                this.ConnectionCompleted(this, new AsyncCompletedEventArgs(null, false, e.UserToken));
            }
        }

        #endregion

        #region IMessageClient Members

        event EventHandler<AsyncCompletedEventArgs> IMessageClient.ConnectionCompleted
        {
            add { throw new NotImplementedException(); }
            remove { throw new NotImplementedException(); }
        }

        event EventHandler<AsyncCompletedEventArgs> IMessageClient.SendCompleted
        {
            add { throw new NotImplementedException(); }
            remove { throw new NotImplementedException(); }
        }

        event EventHandler<EventArgs> IMessageClient.ConnectionLost
        {
            add { throw new NotImplementedException(); }
            remove { throw new NotImplementedException(); }
        }

        event EventHandler<MessageEventArgs> IMessageClient.MessageRecieved
        {
            add { throw new NotImplementedException(); }
            remove { throw new NotImplementedException(); }
        }

        bool IMessageClient.IsConnected
        {
            get { throw new NotImplementedException(); }
        }

        void IMessageClient.ConnectAsync()
        {
            throw new NotImplementedException();
        }

        void IMessageClient.ConnectAsync(object state)
        {
            throw new NotImplementedException();
        }

        void IMessageClient.Disconnect()
        {
            throw new NotImplementedException();
        }

        void IMessageClient.SendMessageAsync(MessageInfo message)
        {
            throw new NotImplementedException();
        }

        void IMessageClient.SendMessageAsync(MessageInfo message, object userState)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
